using System;
using System.Collections.Generic;
using NUnit.Framework;
using Rhino.Mocks;
using Rhino.Mocks.Constraints;

namespace CodeBackpack.Plist.Test
{
    [TestFixture]
    public class PropertyListTestFixture
    {
        private MockRepository mocks;

        [SetUp]
        public void SetUp()
        {
            mocks = new MockRepository();
        }

        [Test]
        public void Ctor()
        {
            PropertyList plist = new PropertyList();
            Assert.IsNull(plist.Value);
        }

        [Test]
        public void CreateList()
        {
            Assert.IsInstanceOfType(typeof (IList<PropertyList>), PropertyList.CreateList().Value);
        }

        [Test]
        public void CreateDict()
        {
            Assert.IsInstanceOfType(typeof (IDictionary<String, PropertyList>), PropertyList.CreateDict().Value);
        }

        [Test]
        public void AssignBytes()
        {
            PropertyList plist = new PropertyList();
            byte[] bytes = new byte[] {1, 2, 3};
            plist.Bytes = bytes;
            Assert.AreEqual(bytes, plist.Bytes);
        }

        [Test]
        public void AssignString()
        {
            PropertyList plist = new PropertyList();
            plist.String = "foo";
            Assert.AreEqual("foo", plist.String);
        }

        [Test]
        public void AssignInt()
        {
            PropertyList plist = new PropertyList();
            plist.Int = 42;
            Assert.AreEqual(42, plist.Int);
        }

        [Test]
        public void CastLongToInt()
        {
            PropertyList plist = new PropertyList();
            plist.Long = 42;
            Assert.AreEqual(42, plist.Int);
        }

        [Test, ExpectedException(typeof (OverflowException))]
        public void TooBigToCastToInt()
        {
            PropertyList plist = new PropertyList();
            plist.Long = 1L + int.MaxValue;
            plist.Int.ToString();
        }

        [Test, ExpectedException(typeof (OverflowException))]
        public void TooSmallToCastToInt()
        {
            PropertyList plist = new PropertyList();
            plist.Long = -1L + int.MinValue;
            plist.Int.ToString();
        }

        [Test]
        public void AssignLong()
        {
            PropertyList plist = new PropertyList();
            plist.Long = 42;
            Assert.AreEqual(42, plist.Long);
        }

        [Test]
        public void CastIntToLong()
        {
            PropertyList plist = new PropertyList();
            plist.Int = 42;
            Assert.AreEqual(42, plist.Long);
        }

        [Test]
        public void AssignDouble()
        {
            PropertyList plist = new PropertyList();
            plist.Double = Math.PI;
            Assert.AreEqual(Math.PI, plist.Double);
        }

        [Test]
        public void AssignDateTime()
        {
            PropertyList plist = new PropertyList();
            DateTime now = DateTime.Now;
            plist.DateTime = now;
            Assert.AreEqual(now, plist.DateTime);
        }

        [Test]
        public void AssignBool()
        {
            PropertyList plist = new PropertyList();
            plist[0].Bool = true;
            plist[1].Bool = false;
            Assert.IsTrue(plist[0].Bool);
            Assert.IsFalse(plist[1].Bool);
        }

        [Test]
        public void LazyListCreation()
        {
            PropertyList plist = new PropertyList();
            Assert.AreEqual(0, plist.List.Count);
            Assert.IsInstanceOfType(typeof(IList<PropertyList>), plist.Value);
        }

        [Test]
        public void AssignListElement()
        {
            PropertyList plist = new PropertyList();
            plist[0].Int = 55;
            Assert.AreEqual(55, plist[0].Int);
        }

        [Test]
        public void SparseList()
        {
            PropertyList plist = new PropertyList();
            plist[41].String = "the answer";
            Assert.AreEqual("the answer", plist[41].String);
            Assert.AreEqual(42, plist.List.Count);
        }

        [Test]
        public void ChangeTypeToList()
        {
            PropertyList plist = new PropertyList();
            plist.Int = 55;
            plist.List.Add(new PropertyList());
        }

        [Test]
        public void LazyDictCreation()
        {
            PropertyList plist = new PropertyList();
            Assert.AreEqual(0, plist.Dict.Count);
            Assert.IsInstanceOfType(typeof (IDictionary<String, PropertyList>), plist.Value);
        }

        [Test]
        public void AssignDictElement()
        {
            PropertyList plist = new PropertyList();
            plist["foo"].Int = 55;
            Assert.AreEqual(55, plist["foo"].Int);
        }

        [Test]
        public void RemoveDictElement()
        {
            PropertyList plist = new PropertyList();
            plist["foo"].Int = 55;
            Assert.AreEqual(1, plist.Dict.Count);
            plist.Dict.Remove("foo");
            Assert.AreEqual(0, plist.Dict.Count);
        }

        [Test]
        public void ChangeTypeToDict()
        {
            PropertyList plist = new PropertyList();
            plist.Int = 55;
            plist.Dict.ContainsKey("whatever");
        }

        [Test]
        public void InsertionOrderIsPreserved()
        {
            PropertyList plist = new PropertyList();
            plist["z"].Int = 1;
            plist["y"].Int = 2;
            plist["foo"].String = "stuff";
            plist["x"].Int = 3;
            plist.Dict.Remove("foo");
            List<String> keys = new List<String>();
            foreach (KeyValuePair<String, PropertyList> pair in plist.Dict)
            {
                keys.Add(pair.Key);
            }
            Assert.AreEqual("z,y,x", String.Join(",", keys.ToArray()));
        }

        [Test]
        public void WritePrologEpilog()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteBytes()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteScalar((byte[]) null); })
                .Constraints(List.Equal(new byte[] {1, 2, 3}));
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist.Bytes = new byte[] {1, 2, 3};
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteString()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteScalar("foo"); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist.String = "foo";
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteInt()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteScalar(42); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist.Int = 42;
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteLong()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteScalar((long) 42); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist.Long = 42;
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteDouble()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteScalar(Math.PI); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist.Double = Math.PI;
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteDateTime()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();
            DateTime t0 = DateTime.Now;

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteScalar(t0.ToUniversalTime()); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist.DateTime = t0;
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteBool()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteScalar(false); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist.Bool = false;
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteList()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteListStart(); });
            Expect.Call(delegate { mockWriter.WriteScalar(true); });
            Expect.Call(delegate { mockWriter.WriteScalar("yes"); });
            Expect.Call(delegate { mockWriter.WriteScalar(1); });
            Expect.Call(delegate { mockWriter.WriteContainerEnd(); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist[0].Bool = true;
            plist[1].String = "yes";
            // we have two empty list elements with indexes 2 & 3, they shouldn't be written.
            plist[4].Int = 1;
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void WriteDict()
        {
            PropertyListWriter mockWriter = mocks.CreateMock<PropertyListWriter>();

            // expectations
            Expect.Call(delegate { mockWriter.WriteProlog(); });
            Expect.Call(delegate { mockWriter.WriteDictStart(); });
            Expect.Call(delegate { mockWriter.WriteDictKey("a"); });
            Expect.Call(delegate { mockWriter.WriteScalar(true); });
            Expect.Call(delegate { mockWriter.WriteDictKey("b"); });
            Expect.Call(delegate { mockWriter.WriteScalar(42); });
            Expect.Call(delegate { mockWriter.WriteContainerEnd(); });
            Expect.Call(delegate { mockWriter.WriteEpilog(); });
            mocks.ReplayAll();

            // execution
            PropertyList plist = new PropertyList();
            plist["a"].Bool = true;
            plist["b"].Int = 42;
            plist.Save(mockWriter);

            // verification
            mocks.VerifyAll();
        }

        [Test]
        public void LoadEmptyPlist()
        {
            PropertyListReader mockPropertyListReader = mocks.CreateMock<PropertyListReader>();

            // expect
            Expect.Call(delegate { mockPropertyListReader.ReadProlog(); });
            Expect.Call(mockPropertyListReader.IsEmpty()).Return(true);
            Expect.Call(delegate { mockPropertyListReader.ReadEpilog(); });

            // execute
            mocks.ReplayAll();
            Assert.IsNull(new PropertyList().Load(mockPropertyListReader).Value);

            // verify
            mocks.VerifyAll();
        }
    }
}